﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Serialization;
using Pixysoft.Framework.Verifications;

namespace Pixysoft.Framework.Reflection.Controller
{
    //http://forums.silverlight.net/forums/p/123922/286800.aspx
    //http://www.rainsts.net/article.asp?id=353

    /// <summary>
    /// 生成一个pojo对象，同时自包含了静态的初始化方法，继承了ICloneable接口
    /// </summary>
    class PojoFactory
    {
        private const string module = "Pixysoft.Framework.Reflection.PojoModule";

        private const string assembly = "Pixysoft.Framework.Reflection.PojoAssembly";

        static AssemblyBuilder assemblyBuilder = null;

        static ModuleBuilder moduleBuilder = null;

        public static object CreatePojo(Type targetType)
        {
            if (!targetType.IsInterface)
                throw new Exception("pojo must be inherit from interface.");


            // create assembly

            assemblyBuilder = GetAssemblyBuilder();

            moduleBuilder = GetModuleBuilder(assemblyBuilder);



            //create type

            TypeBuilder typeBuilder = null;

            if (ReflectionHelper.IsInheritFrom(targetType, typeof(ICloneable)))
            {
                typeBuilder = GetTypeBuilder(moduleBuilder, targetType,
                   null,
                   new Type[] { targetType });
            }
            else
            {
                typeBuilder = GetTypeBuilder(moduleBuilder, targetType,
                   null,
                   new Type[] { targetType, typeof(ICloneable) });
            }

            //create attributre

            EmitHelper.CreateXmlRootAttribute(targetType, typeBuilder);

            EmitHelper.CreateSerializableAttribute(targetType, typeBuilder);


            // create field/property  

            List<FieldBuilder> fieldBuilders = new List<FieldBuilder>();

            List<string> duplicateProperties = new List<string>();

            foreach (PropertyInfo property in Pixysoft.Reflection.ReflectHelper.Instance.GetInterfaceProperties(targetType))
            {
                string propertyName = property.Name.Trim();

                if (duplicateProperties.Contains(propertyName))
                    continue;

                duplicateProperties.Add(propertyName);

                fieldBuilders.Add(CreateProperty(typeBuilder, property));
            }

            // create ctor.

            ConstructorBuilder ctor = CreateDefaultConstructor(typeBuilder, targetType, fieldBuilders);


            //method 同样也要实现 否则会出错

            foreach (MethodInfo method in Pixysoft.Reflection.ReflectHelper.Instance.GetInterfaceMethods(targetType))
            {
                if (Pixysoft.Reflection.ReflectHelper.Instance.IsPropertyMethod(method))
                    continue;

                if (ReflectionHelper.IsCloneMethod(method))
                    continue;

                if (method.ReturnType.Equals(typeof(void)))
                {
                    CreateVoidMethod(typeBuilder, method);
                }
                else
                {
                    CreateReturnMethod(typeBuilder, method);
                }
            }

            //clone method

            CreateCloneMethod(typeBuilder, ctor);

            Type newPojoType = typeBuilder.CreateType();

            return Activator.CreateInstance(newPojoType);
        }

        private static ConstructorBuilder CreateDefaultConstructor(TypeBuilder typeBuilder, Type targetType, List<FieldBuilder> fieldBuilders)
        {
            ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
            ILGenerator il = ctor.GetILGenerator();

            int index = 0;

            foreach (PropertyInfo property in targetType.GetProperties())
            {
                switch (Pixysoft.Tools.ParserHelper.GetPropertyType(property.PropertyType))
                {
                    case DotNetPropertyType.BinaryList:
                    case DotNetPropertyType.EnumList:
                    case DotNetPropertyType.InterfaceList:
                    case DotNetPropertyType.ObjectList:
                    case DotNetPropertyType.ValueList:
                        {
                            OpCodesFactory.LdArgument(il, 0);
                            OpCodesFactory.CreateObject(il, property.PropertyType);
                            OpCodesFactory.StField(il, fieldBuilders[index]);
                            break;
                        }
                    case DotNetPropertyType.EnumArray:
                    case DotNetPropertyType.InterfaceArray:
                    case DotNetPropertyType.ObjectArray:
                    case DotNetPropertyType.ValueArray:
                        {
                            // TODO NOT SUPPORT ARRAY INITIALIZE YET... 2010-09-25

                            break;
                        }
                    default:
                        {
                            break;
                        }
                }

                index++;
            }

            OpCodesFactory.ReturnMethod(il);

            return ctor;
        }

        private static AssemblyBuilder GetAssemblyBuilder()
        {
            if (assemblyBuilder != null)
                return assemblyBuilder;

            AppDomain currentAppDomain = AppDomain.CurrentDomain;

            AssemblyName assemblyName = new AssemblyName();

            assemblyName.Name = assembly;

            assemblyBuilder = currentAppDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            return assemblyBuilder;
        }

        private static ModuleBuilder GetModuleBuilder(AssemblyBuilder assemblyBuilder)
        {
            if (moduleBuilder != null)
                return moduleBuilder;

            moduleBuilder = assemblyBuilder.DefineDynamicModule(module);

            return moduleBuilder;
        }

        private static TypeBuilder GetTypeBuilder(ModuleBuilder moduleBuilder, Type proxyType, Type parentType, Type[] interfaceTypes)
        {
            //类名不能以数字开头 否则序列化是出错

            if (proxyType.IsGenericType)
                throw Exceptions.UnexpectedResultException("do not support generic type in pojo factory.");

            if (string.IsNullOrEmpty(proxyType.Name))
                throw Exceptions.UnexpectedResultException("unknown parent typename in pojo factory. type:{0}", proxyType.Name);

            String newTypeName = "_" + proxyType.Name + "_" + Guid.NewGuid().ToString().Replace("-", "_");

            TypeBuilder typeBuilder = moduleBuilder.DefineType(newTypeName,
                TypeAttributes.Class | TypeAttributes.NotPublic, typeof(object), interfaceTypes);

            return typeBuilder;
        }

        private static FieldBuilder CreateProperty(TypeBuilder typeBuilder, PropertyInfo property)
        {
            string propertyName = property.Name;

            Type propertyType = property.PropertyType;

            MethodAttributes attribute =
                MethodAttributes.Public |
                MethodAttributes.FamANDAssem |
                MethodAttributes.Family |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig |
                MethodAttributes.SpecialName;

            FieldBuilder fieldBuilder = typeBuilder.DefineField(GetFieldName(propertyName), propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);


            if (property.GetGetMethod() != null)
            {
                MethodBuilder getMethod = typeBuilder.DefineMethod("get_" + propertyName, attribute, propertyType, Type.EmptyTypes);
                foreach (ParameterInfo parameter in property.GetGetMethod().GetParameters())
                    getMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

                ILGenerator getIl = getMethod.GetILGenerator();
                OpCodesFactory.LdArgument(getIl, 0);
                OpCodesFactory.LdField(getIl, fieldBuilder);
                OpCodesFactory.ReturnMethod(getIl);
                propertyBuilder.SetGetMethod(getMethod);
            }

            if (property.GetSetMethod() != null)
            {
                MethodBuilder setMethod = typeBuilder.DefineMethod("set_" + propertyName, attribute, null, new Type[] { propertyType });
                foreach (ParameterInfo parameter in property.GetSetMethod().GetParameters())
                    setMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

                ILGenerator setIl = setMethod.GetILGenerator();
                OpCodesFactory.LdArgument(setIl, 0);
                OpCodesFactory.LdArgument(setIl, 1);
                OpCodesFactory.StField(setIl, fieldBuilder);
                OpCodesFactory.ReturnMethod(setIl);
                propertyBuilder.SetSetMethod(setMethod);
            }

            return fieldBuilder;
        }

        private static void CreateReturnMethod(TypeBuilder typeBuilder, MethodInfo method)
        {
            MethodAttributes attribute =
                       MethodAttributes.Public |
                       MethodAttributes.FamANDAssem |
                       MethodAttributes.Family |
                       MethodAttributes.Virtual |
                       MethodAttributes.HideBySig |
                       MethodAttributes.SpecialName;

            List<Type> parameterTypes = new List<Type>();

            foreach (ParameterInfo parameter in method.GetParameters())
                parameterTypes.Add(parameter.ParameterType);

            MethodBuilder setMethod = typeBuilder.DefineMethod(method.Name, attribute, method.ReturnType, parameterTypes.ToArray());
            foreach (ParameterInfo parameter in method.GetParameters())
                setMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

            ILGenerator setIl = setMethod.GetILGenerator();
            OpCodesFactory.LdString(setIl, "not implement in pojo.");
            OpCodesFactory.CreateObject<Exception>(setIl, typeof(string));
            setIl.Emit(OpCodes.Throw);
        }

        private static void CreateVoidMethod(TypeBuilder typeBuilder, MethodInfo method)
        {

            {
                MethodAttributes attribute =
                    MethodAttributes.Public |
                    MethodAttributes.FamANDAssem |
                    MethodAttributes.Family |
                    MethodAttributes.Virtual |
                    MethodAttributes.HideBySig |
                    MethodAttributes.SpecialName;

                List<Type> parameterTypes = new List<Type>();

                foreach (ParameterInfo parameter in method.GetParameters())
                    parameterTypes.Add(parameter.ParameterType);

                //L_0000: nop 
                //L_0001: ldstr "not implement in pojo."
                //L_0006: newobj instance void [mscorlib]System.Exception::.ctor(string)
                //L_000b: throw 

                MethodBuilder setMethod = typeBuilder.DefineMethod(method.Name, attribute, null, parameterTypes.ToArray());
                foreach (ParameterInfo parameter in method.GetParameters())
                    setMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

                ILGenerator setIl = setMethod.GetILGenerator();
                OpCodesFactory.LdString(setIl, "not implement in pojo.");
                OpCodesFactory.CreateObject<Exception>(setIl, typeof(string));
                setIl.Emit(OpCodes.Throw);
            }
        }

        private static string GetFieldName(string propertyName)
        {
            return "_" + propertyName;
        }

        private static void CreateCloneMethod(TypeBuilder typeBuilder, ConstructorBuilder ctor)
        {
            //     ConstructorBuilder ctor =
            //typeBuilder.DefineDefaultConstructor(MethodAttributes.PrivateScope |
            //         MethodAttributes.Public | MethodAttributes.HideBySig |
            //         MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            MethodAttributes attribute =
                    MethodAttributes.Public |
                    MethodAttributes.FamANDAssem |
                    MethodAttributes.Family |
                    MethodAttributes.Virtual |
                    MethodAttributes.HideBySig |
                    MethodAttributes.SpecialName;

            MethodBuilder setMethod = typeBuilder.DefineMethod("Clone", attribute, typeof(object), Type.EmptyTypes);
            ILGenerator setIl = setMethod.GetILGenerator();
            OpCodesFactory.CreateObject(setIl, ctor);
            OpCodesFactory.ReturnMethod(setIl);
        }
    }
}
